﻿//===================================================================================
// Copyright (c) Ryan Garrett.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===================================================================================

//WRITE: After initialization, no other non-state changes should be made to the model. In other words, all injections should happen during initialization
//WRITE: The only known types that will be added by the framework will be enumerations
//WRITE: For structs, they will be added as unconstrained properties unless they implement IEquatable
//WRITE: Commands must always be registered in the same order, otherwise re-serialization will not work.
//WRITE: The de-serialization process is NOT version tolerant
//WRITE: About the parent event cycle.  Added->Purged. Added->Committed->Removed->Purged. This pattern is followed for both parent properties and collections.
//WRITE: Write about how the ParentProperty.IsModelProperty or IsModelCollection can be used instead of the removed ModelBase.IsInCollection property
//WRITE: Write about how the framework prefers 'OnX' overrides to event handlers. If the graph is fast cloned (e.g. the deep bit-for-bit copy), the event handlers will not be connected 
//WRITE: Remember to write about how if a property is not registered for metadata, but is registered for IsDirty, IsDirtyWithin, IsInvalid or IsInvalidWithin, the deserialized graph could be inconsistent.  Partial serializing the graph is dangerous as it's prone to inconsistent deserialized graphs.
//WRITE: Remember when documenting the highlights to point out that the entire graph is serializable without requiring developers to mark their types as Serializable
//WRITE: This is very important. Write about how to deal with Creator and multiple threads. Also the default IModelFactory
//WRITE: Write about how to integrate a container and the ModelFactory. You no longer need to have the container on every model
//WRITE: Inform them of Cntrl R+E shortcut for wrapping backing fields
//DESIGNER: The designer needs to create models that use the constructor which passes in the correct number of registered model properties
//FUTURE: Need to add a Shrink method that resets all of the collections or other potentially large object instances
//FUTURE: Add native support for encryption
//FUTURE: Remember to add support for readonly models
//FUTURE: Create utility that goes through entire assembly looking for types to serialize and caches the type definitions
//FUTURE: Split ModelFactory into two pieces.  ModelFactory and ModelManager.  All shared management operations will go on the ModelManager. It needs to have a flag for controlling all INPC events, complimented with an extension method which raise the INPC event for all models and all properties
//TODO: Test model extensions
//TODO: Test OnMetadataApplied and the Metadata property
//TODO: Test that the UniqueId is preserved for models created from metadata
//TODO: Test the metadata copy process specifically to make sure that parent properties are not assigned for the top most model.
//TODO: Have the Reset operation mute the property validations. This needs to be configurable. What happens is the Reset operation will cause the entire graph to re-validate.  This is generally not needed as in most cases the prior committed state is reliably valid.

using Caffeine.Core.Metadata;
using Caffeine.Core.ModelProperty;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;

namespace Caffeine.Core
{
    /// <summary>
    /// Provides a base class for all models to be derived from. Inheriting your models from this class gives you
    /// access to all of the Caffeine Model Framework's features.
    /// </summary>
    public abstract class ModelBase : INotifyPropertyChanged
    {
        #region State

        private PropertyChangedEventHandler _propertyChanged;
        private ModelBase _parent;
        private ModelPropertyBase _parentProperty;
        private Boolean _hasParent;
        private Boolean _isRemovedFromParent;
        private Boolean _isDirty;
        private Boolean _isDirtyWithin;
        private Boolean _isParentDirtyWithin;
        private Boolean _isInvalid;
        private Boolean _isInvalidWithin;
        private Boolean _isParentInvalidWithin;
        private String _name;
        private Nullable<Guid> _uniqueId;
        private Boolean _isAddedToCollection;
        private Boolean _isCommittedToCollection;
        private Boolean _isRemovedFromCollection;
        private Boolean _isAssignedToParentProperty;
        private Boolean _isCommittedToParentProperty;
        private Boolean _isRemovedFromParentProperty;
        private Int32 _dirtyPropertyCount;
        private Int32 _invalidPropertyCount;
        private Object _modelExtension;
        private readonly IDictionary<String, Object> _extensibleData;
        private readonly List<ModelPropertyBase> _modelProperties;
        private readonly List<ModelCommandBase> _modelCommands;

        #endregion

        #region Constructors

        /// <summary>
        /// Constructor
        /// </summary>
        static ModelBase()
        {
            ModelBaseType = typeof(ModelBase);
            ModelBackingPropertyAttributeType = typeof(ModelBackingPropertyAttribute);
            ModelCommandBackingPropertyAttributeType = typeof(ModelCommandBackingPropertyAttribute);
            ActionType = typeof(Action<,>);
            CanExecuteCommandDelType = typeof(CanExecuteCommandDel<>);

            RegisterPropertyMethod = GetMethod
            (
                ModelBaseType,
                "RegisterProperty", 
                new Type[] { typeof(String), typeof(Nullable<ModelPropertyBehaviors>), typeof(Boolean) }
            );

            RegisterCollectionPropertyMethod = GetMethod
            (
                ModelBaseType,
                "RegisterCollectionProperty",
                new Type[] { typeof(String), typeof(Nullable<ModelPropertyBehaviors>), typeof(Boolean) }
            );

            RegisterStringPropertyMethod = GetMethod
            (
                ModelBaseType,
                "RegisterStringProperty",
                new Type[] { typeof(String), typeof(Nullable<ModelPropertyBehaviors>), typeof(Boolean) }
            );

            RegisterNullableValueTypeMethod = GetMethod
            (
                ModelBaseType,
                "RegisterNullableValueTypeProperty",
                new Type[] { typeof(String), typeof(Nullable<ModelPropertyBehaviors>), typeof(Boolean) }
            );

            RegisterValueTypeMethod = GetMethod
            (
                ModelBaseType,
                "RegisterValueTypeProperty",
                new Type[] { typeof(String), typeof(Nullable<ModelPropertyBehaviors>), typeof(Boolean) }
            );

            RegisterChildModelMethod = GetMethod
            (
                ModelBaseType,
                "RegisterChildModelProperty",
                new Type[] { typeof(String), typeof(Nullable<ModelPropertyBehaviors>), typeof(Boolean) }
            );

            RegisterUnconstrainedMethod = GetMethod
            (
                ModelBaseType,
                "RegisterUnconstrainedProperty",
                new Type[] { typeof(String), typeof(Nullable<ModelPropertyBehaviors>), typeof(Boolean) }
            );

            RegisterEnumMethod = GetMethod
            (
                ModelBaseType,
                "RegisterEnumProperty",
                new Type[] { typeof(String), typeof(Nullable<ModelPropertyBehaviors>), typeof(Boolean) }
            );

            RegisterNullableEnumMethod = GetMethod
            (
                ModelBaseType,
                "RegisterNullableEnumProperty",
                new Type[] { typeof(String), typeof(Nullable<ModelPropertyBehaviors>), typeof(Boolean) }
            );

            RegisterCommandMethod = GetRegisterCommandMethod(ModelBaseType);
        }

        /// <summary>
        /// Constructor
        /// </summary>
        protected ModelBase() : this(0, 0)
        {
        }

        /// <summary>
        /// Constructor. If your model has a lot of properties registered with it, passing the count of properties
        /// can speed up the creation of the model.
        /// </summary>
        /// <param name="modelPropertyCount">The number of properties that will be registered with this model</param>
        /// <param name="modelPropertyCount">The number of command that will be registered with this model</param>
        protected ModelBase(Int32 modelPropertyCount, Int32 modelCommandCount)
        {
            _extensibleData = new Dictionary<String, Object>();
            _modelProperties = new List<ModelPropertyBase>(modelPropertyCount);
            _modelCommands = new List<ModelCommandBase>(modelCommandCount);
            RegisterAdornedFields();
            RegisterAdornedCommands();
        }

        #endregion

        #region Public

        /// <summary>
        /// Gets all possible behaviors used by a model property
        /// </summary>
        public const ModelPropertyBehaviors StandardBehaviors =  ModelPropertyBehaviors.SetsIsDirty      | ModelPropertyBehaviors.SetsParentIsDirty            |
                                                                 ModelPropertyBehaviors.SetsIsInvalid    | ModelPropertyBehaviors.SetsParentIsInvalid          |
                                                                 ModelPropertyBehaviors.IsResettable     | ModelPropertyBehaviors.SupportsPropertyChangeCounting;
        /// <summary>
        /// Raised whenever an observable property has changed
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged
        {
            add
            {
                _propertyChanged += value;
            }
            remove
            {
                _propertyChanged -= value;
            }
        }

        /// <summary>
        /// Gets whether or not this model is dirty. This property supports INPC.
        /// </summary>
        public Boolean IsDirty
        {
            get
            {
                return _isDirty;
            }
            private set
            {
                if (_isDirty != value)
                {
                    _isDirty = value;
                    RaisePropertyChanged("IsDirty");
                    OnIsDirtyChanged();
                    AssignIsParentDirtyWithin();
                }
            }
        }

        /// <summary>
        /// Gets whether or not this model has any children, immediate or distant, that are dirty. This property supports INPC.
        /// </summary>
        public Boolean IsDirtyWithin
        {
            get
            {
                return _isDirtyWithin;
            }
            private set
            {
                if (_isDirtyWithin != value)
                {
                    _isDirtyWithin = value;
                    RaisePropertyChanged("IsDirtyWithin");
                    OnIsDirtyWithinChanged();
                    AssignIsParentDirtyWithin();
                }
            }
        }

        /// <summary>
        /// Gets whether or not this model is invalid. This property supports INPC.
        /// </summary>
        public Boolean IsInvalid
        {
            get
            {
                return _isInvalid;
            }
            private set
            {
                if (_isInvalid != value)
                {
                    _isInvalid = value;
                    RaisePropertyChanged("IsInvalid");
                    OnIsInvalidChanged();
                    AssignIsParentInvalidWithin();
                }
            }
        }

        /// <summary>
        /// Gets whether or not this model has any children, immediate or distant, that are invalid. This property supports INPC.
        /// </summary>
        public Boolean IsInvalidWithin
        {
            get
            {
                return _isInvalidWithin;
            }
            private set
            {
                if (_isInvalidWithin != value)
                {
                    _isInvalidWithin = value;
                    RaisePropertyChanged("IsInvalidWithin");
                    OnIsInvalidWithinChanged();
                    AssignIsParentInvalidWithin();
                }
            }
        }

        /// <summary>
        /// Gets whether or not this model has a parent. This property supports INPC.
        /// </summary>
        public Boolean HasParent
        {
            get
            {
                return _hasParent;
            }
            private set
            {
                if (_hasParent != value)
                {
                    _hasParent = value;
                    RaisePropertyChanged("HasParent");
                }
            }
        }

        /// <summary>
        /// Gets whether or not this model is removed from either a Caffeine.Core.ModelProperty or a Caffeine.Core.ModelCollection property. 
        /// This property supports INPC.
        /// </summary>
        public Boolean IsRemovedFromParent
        {
            get
            {
                return _isRemovedFromParent;
            }
            private set
            {
                if (_isRemovedFromParent != value)
                {
                    _isRemovedFromParent = value;
                    RaisePropertyChanged("IsRemovedFromParent");
                }
            }
        }

        /// <summary>
        /// Gets whether or not this model is added to a Caffeine.Core.ModelCollection property. This property supports INPC.
        /// </summary>
        public Boolean IsAddedToCollection
        {
            get
            {
                return _isAddedToCollection;
            }
            internal set
            {
                if (_isAddedToCollection != value)
                {
                    _isAddedToCollection = value;
                    RaisePropertyChanged("IsAddedToCollection");
                }
            }
        }

        /// <summary>
        /// Gets whether or not this model is committed to a Caffeine.Core.ModelCollection property. This property supports INPC.
        /// </summary>
        public Boolean IsCommittedToCollection
        {
            get
            {
                return _isCommittedToCollection;
            }
            internal set
            {
                if (_isCommittedToCollection != value)
                {
                    _isCommittedToCollection = value;
                    RaisePropertyChanged("IsCommittedToCollection");
                }
            }
        }

        /// <summary>
        /// Gets whether or not this model is removed from a Caffeine.Core.ModelCollection property. This property supports INPC.
        /// </summary>
        public Boolean IsRemovedFromCollection
        {
            get
            {
                return _isRemovedFromCollection;
            }
            internal set
            {
                if (_isRemovedFromCollection != value)
                {
                    _isRemovedFromCollection = value;
                    RaisePropertyChanged("IsRemovedFromCollection");
                    AssignIsRemovedFromParent();
                }
            }
        }

        /// <summary>
        /// Gets whether or not this model is assigned to a Caffeine.Core.ModelProperty. This property supports INPC.
        /// </summary>
        public Boolean IsAssignedToParentProperty
        {
            get
            {
                return _isAssignedToParentProperty;
            }
            internal set
            {
                if (_isAssignedToParentProperty != value)
                {
                    _isAssignedToParentProperty = value;
                    RaisePropertyChanged("IsAssignedToParentProperty");
                }
            }
        }

        /// <summary>
        /// Gets whether or not this model is committed to a Caffeine.Core.ModelProperty. This property supports INPC.
        /// </summary>
        public Boolean IsCommittedToParentProperty
        {
            get
            {
                return _isCommittedToParentProperty;
            }
            internal set
            {
                if (_isCommittedToParentProperty != value)
                {
                    _isCommittedToParentProperty = value;
                    RaisePropertyChanged("IsCommittedToParentProperty");
                }
            }
        }

        /// <summary>
        /// Gets whether or not this model is removed from a Caffeine.Core.ModelProperty. This property supports INPC.
        /// </summary>
        public Boolean IsRemovedFromParentProperty
        {
            get
            {
                return _isRemovedFromParentProperty;
            }
            internal set
            {
                if (_isRemovedFromParentProperty != value)
                {
                    _isRemovedFromParentProperty = value;
                    RaisePropertyChanged("IsRemovedFromParentProperty");
                    AssignIsRemovedFromParent();
                }
            }
        }

        /// <summary>
        /// Gets whether or not the Initialize() method has been called
        /// </summary>
        public Boolean IsInitialized
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets the number of dirty properties this model contains. This property supports INPC.
        /// </summary>
        public Int32 DirtyPropertyCount
        {
            get
            {
                return _dirtyPropertyCount;
            }
            private set
            {
                if (_dirtyPropertyCount != value)
                {
                    _dirtyPropertyCount = value;
                    RaisePropertyChanged("DirtyPropertyCount");
                }
            }
        }

        /// <summary>
        /// Gets the number of invalid properties this model contains. This property supports INPC.
        /// </summary>
        public Int32 InvalidPropertyCount
        {
            get
            {
                return _invalidPropertyCount;
            }
            private set
            {
                if (_invalidPropertyCount != value)
                {
                    _invalidPropertyCount = value;
                    RaisePropertyChanged("InvalidPropertyCount");
                }
            }
        }

        /// <summary>
        /// Gets or sets the name of this model. This property supports INPC.
        /// </summary>
        public String Name
        {
            get
            {
                return _name;
            }
            set
            {
                if (_name != value)
                {
                    _name = value;
                    RaisePropertyChanged("Name");
                }
            }
        }

        /// <summary>
        /// Gets or sets the unique id for this model
        /// </summary>
        public Nullable<Guid> UniqueId
        {
            get
            {
                return _uniqueId;
            }
            set
            {
                if (_uniqueId != value)
                {
                    _uniqueId = value;
                    RaisePropertyChanged("UniqueId");
                }
            }
        }

        /// <summary>
        /// Gets the parent of this model. This property supports INPC.
        /// </summary>
        public ModelBase Parent
        {
            get
            {
                return _parent;
            }
        }

        /// <summary>
        /// Gets the parent property of this model. This property supports INPC.
        /// </summary>
        public ModelPropertyBase ParentProperty
        {
            get
            {
                return _parentProperty;
            }
            private set
            {
                if (_parentProperty != value)
                {
                    _parentProperty = value;
                    RaisePropertyChanged("ParentProperty");
                }
            }
        }

        /// <summary>
        /// Gets or sets the property change counter used by this model's properties
        /// </summary>
        public IPropertyChangeCounter PropertyChangeCounter
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the exception handler for model commands
        /// </summary>
        public ICommandExceptionHandler CommandExceptionHandler
        {
            get;
            set;
        }

        /// <summary>
        /// Gets the properties that are registered to this model
        /// </summary>
        public IEnumerable<ModelPropertyBase> Properties
        {
            get
            {
                return ModelProperties;
            }
        }

        /// <summary>
        /// Gets the commands that are registered to this model
        /// </summary>
        public IEnumerable<ModelCommandBase> Commands
        {
            get
            {
                return ModelCommands;
            }
        }

        /// <summary>
        /// Gets a dictionary of extensible information. If, when hydrating this model, the data transfer objects
        /// you're using contain more information than what this model requires, you can place the extra information in this dictionary.
        /// </summary>
        public IDictionary<String, Object> ExtensibleData
        {
            get
            {
                return _extensibleData;
            }
        }

        /// <summary>
        /// Gets or sets the factory that was used to create this model
        /// </summary>
        public IModelFactory ParentFactory
        {
            get;
            set;
        }

        /// <summary>
        /// Gets the extension object for this model. This model type must have an extension method registered
        /// with the ModelFactory associated with this instance.
        /// </summary>
        public Object ModelExtension
        {
            get
            {
                if (_modelExtension == null)
                {
                    Func<ModelBase, Object> extensionMethod;

                    if 
                    (
                        ParentFactory.ModelExtensions.TryGetValue(GetType(), out extensionMethod) &&
                        extensionMethod != null
                    )
                    {
                        _modelExtension = extensionMethod(this);
                    }
                }

                return _modelExtension;
            }
            private set
            {
                if (_modelExtension != value)
                {
                    _modelExtension = value;
                    RaisePropertyChanged("ModelExtension");
                }
            }
        }

        /// <summary>
        /// Reset all registered properties to their previous committed state
        /// </summary>
        public virtual void Reset()
        {
            foreach (ModelPropertyBase property in ModelProperties)
            {
                if (property.CheckIsResettable())
                {
                    property.Reset();
                }
            }
        }

        /// <summary>
        /// Commit all registered properties
        /// </summary>
        public virtual void Commit()
        {
            foreach (ModelPropertyBase property in ModelProperties)
            {
                property.Commit();
            }
        }

        /// <summary>
        /// Validate all registered properties
        /// </summary>
        /// <returns>A dictionary containing any invalid model properties and their respective errors</returns>
        public virtual IDictionary<ModelPropertyBase, IEnumerable<ValidationError>> Validate()
        {
            Dictionary<ModelPropertyBase, IEnumerable<ValidationError>> invalidProperties = new Dictionary<ModelPropertyBase, IEnumerable<ValidationError>>();

            foreach (ModelPropertyBase property in ModelProperties)
            {
                property.Validate();

                if (property.IsInvalid)
                {
                    invalidProperties[property] = property.ValidationErrors ?? Enumerable.Empty<ValidationError>();
                }
            }

            return invalidProperties;
        }

        /// <summary>
        /// Release all event handlers registered to receive property change notifications
        /// </summary>
        public virtual void ReleaseObservers()
        {
            _propertyChanged = null;

            foreach (ModelPropertyBase property in ModelProperties)
            {
                property.ReleaseObservers();
            }

            foreach (ModelCommandBase command in ModelCommands)
            {
                command.ReleaseObservers();
            }
        }

        /// <summary>
        /// Clears the extension assigned to this model, if there is one. If the model extension implements
        /// IModelExtension, its release method is called. This method will set the ModelExtension property to null.
        /// </summary>
        public virtual void ClearModelExtension()
        {
            if (_modelExtension != null && _modelExtension is IModelExtension)
            {
                (_modelExtension as IModelExtension).Release();
            }

            ModelExtension = null;
        }

        /// <summary>
        /// Assign all registered properties to their default values
        /// </summary>
        public virtual void AssignDefaultValues()
        {
            foreach (ModelPropertyBase property in Properties)
            {
                property.AssignDefaultValue();
            }
        }

        /// <summary>
        /// Adds an error to the collection passed in. If the <para>errors</para> parameter is null, it will be assigned an instance of a collection.
        /// </summary>
        /// <param name="errors">Errors collection</param>
        /// <param name="errorCode">Error code</param>
        /// <param name="errorMessage">Error message</param>
        public virtual void AddError(ref IEnumerable<ValidationError> errors, Int32 errorCode, String errorMessage)
        {
            if (errors == null)
            {
                errors = CreateValidationErrorList();
            }

            ((IList<ValidationError>)errors).Add(new ValidationError() { ErrorCode = errorCode, ErrorDescription = errorMessage });
        }

        /// <summary>
        /// Clean the model object
        /// </summary>
        public virtual void Clean()
        {
        }

        /// <summary>
        /// Raise the PropertyChanged event for some property that has changed
        /// </summary>
        /// <param name="propertyName">Name of property that has changed</param>
        public virtual void RaisePropertyChanged(String propertyName)
        {
            if (_propertyChanged != null)
            {
                _propertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        public virtual ModelMetadata GetMetadata()
        {
            ModelMetadata metadata = new ModelMetadata()
            {
                IsDirty = IsDirty,
                IsDirtyWithin = IsDirtyWithin,
                IsInvalid = IsInvalid,
                IsInvalidWithin = IsInvalidWithin,
                HasParent = HasParent,
                IsRemovedFromParent = IsRemovedFromParent,
                IsAddedToCollection = IsAddedToCollection,
                IsCommittedToCollection = IsCommittedToCollection,
                IsRemovedFromCollection = IsRemovedFromCollection,
                IsAssignedToParentProperty = IsAssignedToParentProperty,
                IsCommittedToParentProperty = IsCommittedToParentProperty,
                IsRemovedFromParentProperty = IsRemovedFromParentProperty,
                DirtyPropertyCount = DirtyPropertyCount,
                InvalidPropertyCount = InvalidPropertyCount,
                Name = Name,
                UniqueId = UniqueId,
                IsParentDirtyWithin = IsParentDirtyWithin,
                IsParentInvalidWithin = IsParentInvalidWithin,
                DirtyWithinPropertyCount = DirtyWithinPropertyCount,
                InvalidWithinPropertyCount = InvalidWithinPropertyCount,
                FullyQualifiedTypeName = GetType().AssemblyQualifiedName
            };

            if (ExtensibleData.Count > 0)
            {
                metadata.ExtensibleData = new Dictionary<String, Object>(ExtensibleData);
            }

            if (ModelProperties.Count > 0)
            {
                List<ModelScalarPropertyMetadata> scalarPropertyMetadata = new List<ModelScalarPropertyMetadata>();
                List<ModelCollectionMetadata> collectionPropertyMetadata = new List<ModelCollectionMetadata>();

                for (Int32 propertyIndex = 0; propertyIndex < ModelProperties.Count; propertyIndex++)
                {
                    ModelPropertyBase property = ModelProperties[propertyIndex];
                    ModelPropertyBaseMetadata propertyMetadata = null;

                    if (!property.IsModelCollectionProperty)
                    {
                        propertyMetadata = (property as ModelScalarPropertyBase).GetMetadata();
                        scalarPropertyMetadata.Add(propertyMetadata as ModelScalarPropertyMetadata);
                    }
                    else
                    {
                        propertyMetadata = (property as ModelCollectionBase).GetMetadata();
                        collectionPropertyMetadata.Add(propertyMetadata as ModelCollectionMetadata);
                    }

                    propertyMetadata.PropertyIndex = propertyIndex;
                }

                if (scalarPropertyMetadata.Count > 0)
                {
                    metadata.ScalarProperties = scalarPropertyMetadata.ToArray();
                }

                if (collectionPropertyMetadata.Count > 0)
                {
                    metadata.CollectionProperties = collectionPropertyMetadata.ToArray();
                }
            }

            if (ModelCommands.Count > 0)
            {
                ModelCommandMetadata[] modelCommandMetadata = new ModelCommandMetadata[ModelCommands.Count];

                for (Int32 commandIndex = 0; commandIndex < ModelCommands.Count; commandIndex++)
                {
                    modelCommandMetadata[commandIndex] = ModelCommands[commandIndex].GetMetadata();
                }

                metadata.Commands = modelCommandMetadata;
            }

            return metadata;
        }

        #endregion

        #region Internal

        #region IsDirty and IsInvalid

        internal Boolean IsParentDirtyWithin
        {
            get
            {
                return _isParentDirtyWithin;
            }
            private set
            {
                if (_isParentDirtyWithin != value)
                {
                    _isParentDirtyWithin = value;

                    if (Parent != null && !IsRemovedFromParent)
                    {
                        Parent.SignalIsDirtyWithinChange(value);
                    }
                }
            }
        }

        internal Boolean IsParentInvalidWithin
        {
            get
            {
                return _isParentInvalidWithin;
            }
            private set
            {
                if (_isParentInvalidWithin != value)
                {
                    _isParentInvalidWithin = value;

                    if (Parent != null && !IsRemovedFromParent)
                    {
                        Parent.SignalIsInvalidWithinChange(value);
                    }
                }
            }
        }

        internal Int32 DirtyWithinPropertyCount
        {
            get;
            set;
        }

        internal Int32 InvalidWithinPropertyCount
        {
            get;
            set;
        }

        internal void SignalIsDirtyChange(Boolean isDirty)
        {
            if (isDirty)
            {
                DirtyPropertyCount++;
            }
            else
            {
                DirtyPropertyCount--;
            }

            IsDirty = DirtyPropertyCount > 0;
        }

        internal void SignalIsDirtyWithinChange(Boolean isDirtyWithin)
        {
            if (isDirtyWithin)
            {
                DirtyWithinPropertyCount++;
            }
            else
            {
                DirtyWithinPropertyCount--;
            }

            IsDirtyWithin = DirtyWithinPropertyCount > 0;
        }

        internal void SignalIsInvalidChange(Boolean isInvalid)
        {
            if (isInvalid)
            {
                InvalidPropertyCount++;
            }
            else
            {
                InvalidPropertyCount--;
            }

            IsInvalid = InvalidPropertyCount > 0;
        }

        internal void SignalIsInvalidWithinChange(Boolean isInvalidWithin)
        {
            if (isInvalidWithin)
            {
                InvalidWithinPropertyCount++;
            }
            else
            {
                InvalidWithinPropertyCount--;
            }

            IsInvalidWithin = InvalidWithinPropertyCount > 0;
        }

        internal void Initialize(Boolean validateRegisteredProperties, ModelMetadata metadata, ModelPropertyBase parentProperty)
        {
            if (validateRegisteredProperties)
            {
                ValidateRegisteredProperties();
            }

            IsInitialized = true;
            SourceMetadata = metadata;
            OnInitialized();

            if (metadata != null)
            {
                ApplyMetadata(metadata, parentProperty);
                OnMetadataApplied();
            }

            SourceMetadata = null;
        }

        #endregion

        #region Parent Collection Properties

        internal void RaiseOnAddedToCollection(ModelCollectionBase parentCollection)
        {
            OnParentCollectionChanged(parentCollection);
            OnAddedToCollection(parentCollection);
        }

        internal void RaiseOnCommittedToCollection(ModelCollectionBase parentCollection)
        {
            OnParentCollectionChanged(parentCollection);
            OnCommittedToCollection(parentCollection);
        }

        internal void RaiseOnMarkedForRemovalFromCollection(ModelCollectionBase parentCollection)
        {
            OnParentCollectionChanged(parentCollection);
            OnMarkedForRemovalFromCollection(parentCollection);
        }

        internal void RaiseOnRemovedFromCollection(ModelCollectionBase parentCollection)
        {
            OnParentCollectionChanged(parentCollection);
            OnRemovedFromCollection(parentCollection);
        }

        #endregion

        #region Parent Properties

        internal void RaiseOnAssignedToProperty(ModelScalarPropertyBase parentProperty)
        {
            OnParentPropertyChanged(parentProperty);
            OnAssignedToProperty(parentProperty);
        }

        internal void RaiseOnCommittedToProperty(ModelScalarPropertyBase parentProperty)
        {
            OnParentPropertyChanged(parentProperty);
            OnCommittedToProperty(parentProperty);
        }

        internal void RaiseOnMarkedForRemovalFromProperty(ModelScalarPropertyBase parentProperty)
        {
            OnParentPropertyChanged(parentProperty);
            OnMarkedForRemovalFromProperty(parentProperty);
        }

        internal void RaiseOnRemovedFromProperty(ModelScalarPropertyBase parentProperty)
        {
            OnParentPropertyChanged(parentProperty);
            OnRemovedFromParentProperty(parentProperty);
        }

        internal void SignalRestoredToParentProperty(Boolean setParentIsDirtyWithin, Boolean setParentIsInvalidWithin)
        {
            if (setParentIsDirtyWithin && IsParentDirtyWithin)
            {
                _parent.SignalIsDirtyWithinChange(true);
            }

            if (setParentIsInvalidWithin && IsParentInvalidWithin)
            {
                _parent.SignalIsInvalidWithinChange(true);
            }
        }

        internal void SignalRemovedFromParentProperty(Boolean setParentIsDirtyWithin, Boolean setParentIsInvalidWithin)
        {
            if (setParentIsDirtyWithin && IsParentDirtyWithin)
            {
                _parent.SignalIsDirtyWithinChange(false);
            }

            if (setParentIsInvalidWithin && IsParentInvalidWithin)
            {
                _parent.SignalIsInvalidWithinChange(false);
            }
        }

        internal void SetParent(ModelBase value, ModelPropertyBase parentProperty, Boolean setParentIsDirtyWithin, Boolean setParentIsInvalidWithin)
        {
            if (_parent != value)
            {
                OnBeforeParentChanged();

                if (value == null)
                {
                    HasParent = false;
                    ParentProperty = parentProperty;
                }

                if (_parent != null)
                {
                    if (setParentIsDirtyWithin && IsParentDirtyWithin)
                    {
                        _parent.SignalIsDirtyWithinChange(false);
                    }

                    if (setParentIsInvalidWithin && IsParentInvalidWithin)
                    {
                        _parent.SignalIsInvalidWithinChange(false);
                    }
                }

                _parent = value;
                RaisePropertyChanged("Parent");
                
                if (_parent != null)
                {
                    if (setParentIsDirtyWithin && IsParentDirtyWithin)
                    {
                        _parent.SignalIsDirtyWithinChange(true);
                    }

                    if (setParentIsInvalidWithin && IsParentInvalidWithin)
                    {
                        _parent.SignalIsInvalidWithinChange(true);
                    }
                }

                if (value != null)
                {
                    ParentProperty = parentProperty;
                    HasParent = true;
                }

                OnParentChanged();
            }
        }

        #endregion

        #region Registered Properties

        internal List<ModelPropertyBase> ModelProperties
        {
            get
            {
                return _modelProperties;
            }
        }

        internal List<ModelCommandBase> ModelCommands
        {
            get
            {
                return _modelCommands;
            }
        }

        internal void RaiseOnPropertyIsDefaultChanged(ModelScalarPropertyBase property)
        {
            OnPropertyIsDefaultChanged(property);
        }

        internal void RaiseOnPropertyIsDirtyChanged(ModelPropertyBase property)
        {
            OnPropertyIsDirtyChanged(property);
        }

        internal void RaiseOnPropertyIsInvalidChanged(ModelPropertyBase property)
        {
            OnPropertyIsInvalidChanged(property);
        }

        internal void RaiseOnPropertyValueChanged(ModelScalarPropertyBase property)
        {
            OnPropertyValueChanged(property);
        }

        internal void RaiseOnPropertyCommittedValueChanged(ModelScalarPropertyBase property)
        {
            OnPropertyCommittedValueChanged(property);
        }

        #endregion

        #region Registration Helper Properties

        internal static Boolean CommandDefaultCanExecuteCheck<T>(ModelBase parent, T parameter)
        {
            return true;
        }

        #endregion

        #endregion

        #region Protected

        /// <summary>
        /// Gets the metadata being used to create this model. This property is only non-null if the model is being created from
        /// metadata and during the OnInitialized and OnMetadataApplied methods. After the OnMetadataApplied method, this property
        /// will return to null. DO NOT MAKE ANY CHANGES TO THE METADATA UNLESS YOU ARE VERY SURE OF WHAT YOU ARE DOING!!
        /// </summary>
        protected ModelMetadata SourceMetadata
        {
            get;
            private set;
        }

        #region On[Event] Notification Methods

        /// <summary>
        /// Raised after initialization
        /// </summary>
        protected virtual void OnInitialized()
        {
        }

        /// <summary>
        /// Raised after the metadata for this model has been applied. This will always be raised after OnInitialized.
        /// </summary>
        protected virtual void OnMetadataApplied()
        {
        }

        /// <summary>
        /// Called right before the Parent property of this model is changed
        /// </summary>
        protected virtual void OnBeforeParentChanged()
        {
        }

        /// <summary>
        /// Called directly after the Parent property of this model has changed
        /// </summary>
        protected virtual void OnParentChanged()
        {
        }

        #region IsDirty and IsInvalid

        /// <summary>
        /// Called when the IsDirty property value has changed
        /// </summary>
        protected virtual void OnIsDirtyChanged()
        {
        }

        /// <summary>
        /// Called when the IsDirtyWithin property value has changed
        /// </summary>
        protected virtual void OnIsDirtyWithinChanged()
        {
        }

        /// <summary>
        /// Called when the IsInvalid property value has changed
        /// </summary>
        protected virtual void OnIsInvalidChanged()
        {
        }

        /// <summary>
        /// Called when the IsInvalidWithin property value has changed
        /// </summary>
        protected virtual void OnIsInvalidWithinChanged()
        {
        }

        #endregion

        #region Collections

        /// <summary>
        /// Called whenever this model's relationship with its Caffeine.Core.ModelCollectionBase parent property has changed
        /// </summary>
        /// <param name="parentCollectionProperty">Caffeine.Core.ModelCollectionBase containing this model</param>
        protected virtual void OnParentCollectionChanged(ModelCollectionBase parentCollectionProperty)
        {
        }

        /// <summary>
        /// Called when this model is added to a Caffeine.Core.ModelCollectionBase parent property
        /// </summary>
        /// <param name="parentCollectionProperty">Caffeine.Core.ModelCollectionBase containing this model</param>
        protected virtual void OnAddedToCollection(ModelCollectionBase parentCollectionProperty)
        {
        }

        /// <summary>
        /// Called when this model is committed to a Caffeine.Core.ModelCollectionBase parent property.
        /// </summary>
        /// <param name="parentCollectionProperty">Caffeine.Core.ModelCollectionBase containing this model</param>
        protected virtual void OnCommittedToCollection(ModelCollectionBase parentCollectionProperty)
        {
        }

        /// <summary>
        /// Called when this model is removed from a Caffeine.Core.ModelCollectionBase parent property, after already being committed to it
        /// </summary>
        /// <param name="parentCollectionProperty">Caffeine.Core.ModelCollectionBase containing this model</param>
        protected virtual void OnMarkedForRemovalFromCollection(ModelCollectionBase parentCollectionProperty)
        {
        }

        /// <summary>
        /// Called when this model is removed from a Caffeine.Core.ModelCollectionBase parent property, 
        /// after already being marked for removal
        /// </summary>
        /// <param name="parentCollectionProperty">Caffeine.Core.ModelCollectionBase no longer containing this model</param>
        protected virtual void OnRemovedFromCollection(ModelCollectionBase parentCollectionProperty)
        {
        }

        #endregion

        #region Parent Properties

        /// <summary>
        /// Called whenever this model's relationship with its Caffeine.Core.ModelScalarPropertyBase parent property has changed
        /// </summary>
        /// <param name="parentProperty">Caffeine.Core.ModelScalarPropertyBase property containing this model</param>
        protected virtual void OnParentPropertyChanged(ModelScalarPropertyBase parentProperty)
        {
        }

        /// <summary>
        /// Called when this model is assigned to a Caffeine.Core.ModelScalarPropertyBase parent property
        /// </summary>
        /// <param name="parentProperty">Caffeine.Core.ModelScalarPropertyBase property containing this model</param>
        protected virtual void OnAssignedToProperty(ModelScalarPropertyBase parentProperty)
        {
        }

        /// <summary>
        /// Called when this model is committed to a Caffeine.Core.ModelScalarPropertyBase parent property
        /// </summary>
        /// <param name="parentProperty">Caffeine.Core.ModelScalarPropertyBase property containing this model</param>
        protected virtual void OnCommittedToProperty(ModelScalarPropertyBase parentProperty)
        {
        }

        /// <summary>
        /// Called when this model is removed from a Caffeine.Core.ModelScalarPropertyBase parent property, after already being committed to it
        /// </summary>
        /// <param name="parentProperty">Caffeine.Core.ModelScalarPropertyBase property containing this model</param>
        protected virtual void OnMarkedForRemovalFromProperty(ModelScalarPropertyBase parentProperty)
        {
        }

        /// <summary>
        /// Called when this model is removed from a Caffeine.Core.ModelScalarPropertyBase parent property, 
        /// after already being marked for removal
        /// </summary>
        /// <param name="parentCollectionProperty">Caffeine.Core.ModelScalarPropertyBase no longer containing this model</param>
        protected virtual void OnRemovedFromParentProperty(ModelScalarPropertyBase parentProperty)
        {
        }

        #endregion

        #region Registered Properties

        /// <summary>
        /// Called whenever a registered property is changed to or from it's default value
        /// </summary>
        /// <param name="property">Property that has changed</param>
        protected virtual void OnPropertyIsDefaultChanged(ModelScalarPropertyBase property)
        {
        }

        /// <summary>
        /// Called whenever a registered property's IsDirty flag has changed
        /// </summary>
        /// <param name="property">Property that has changed</param>
        protected virtual void OnPropertyIsDirtyChanged(ModelPropertyBase property)
        {
        }

        /// <summary>
        /// Called whenever a registered property's IsInvalid flag has changed
        /// </summary>
        /// <param name="property">Property that has changed</param>
        protected virtual void OnPropertyIsInvalidChanged(ModelPropertyBase property)
        {
        }

        /// <summary>
        /// Called whenever a registered property's Value property has changed
        /// </summary>
        /// <param name="property">Property that has changed</param>
        protected virtual void OnPropertyValueChanged(ModelScalarPropertyBase property)
        {
        }

        /// <summary>
        /// Called whenever a registered property's CommittedValue property has changed
        /// </summary>
        /// <param name="property">Property that has changed</param>
        protected virtual void OnPropertyCommittedValueChanged(ModelScalarPropertyBase property)
        {
        }

        #endregion

        #endregion

        #region Registration Methods

        /// <summary>
        /// Validates all model properties are registered correctly
        /// </summary>
        protected void ValidateRegisteredProperties()
        {
            if (ModelProperties.Count > 0)
            {
                IEnumerable<PropertyInfo> properties = GetType().GetRuntimeProperties();

                for (Int32 index = 0; index < ModelProperties.Count; index++)
                {
                    ModelPropertyBase modelProperty = ModelProperties[index];
                    
                    //Duplicate property check
                    for (Int32 modelPropertyIndex = 0; modelPropertyIndex < ModelProperties.Count; modelPropertyIndex++)
                    {
                        if (index != modelPropertyIndex && ModelProperties[modelPropertyIndex].Name == modelProperty.Name)
                        {
                            throw new InvalidOperationException
                            (
                                String.Format
                                (
                                    "Model property '{0}' registered more than once in type '{1}'",
                                    modelProperty.Name,
                                    GetType().FullName
                                )
                            );
                        }
                    }

                    Boolean foundProperty = false;

                    foreach (PropertyInfo property in properties)
                    {
                        if (modelProperty.Name == property.Name)
                        {
                            MethodInfo getMethod = property.GetMethod;

                            //NOTE: The property must have a get 
                            foundProperty = getMethod != null &&
                                            modelProperty.Equals(getMethod.Invoke(this, null));
                        }

                        if (foundProperty)
                        {
                            break;
                        }
                    }

                    if (!foundProperty)
                    {
                        throw new InvalidOperationException
                        (
                            String.Format
                            (
                                "Model property validation failed. Could not find a public property named '{0}' of type '{1}' with a public get method which returned the correct ModelProperty<T> instance in type '{2}'.",
                                modelProperty.Name,
                                modelProperty.GetType().FullName,
                                GetType().FullName
                            )
                        );
                    }
                }
            }
        }

        /// <summary>
        /// Registers a property to this model. This is the recommended method for registering model properties.
        /// </summary>
        /// <typeparam name="T">Type of property to register. This can be any type.</typeparam>
        /// <param name="name">Name of the property to register. The name used to register this property must match the name of a property defined in this class definition.</param>
        /// <returns>The registered property. The return value of this method should be assigned to a readonly backing property.</returns>
        protected ModelProperty<T> RegisterProperty<T>(String name)
        {
            return RegisterProperty<T>(name, StandardBehaviors);
        }

        /// <summary>
        /// Registers a property to this model.
        /// </summary>
        /// <typeparam name="T">Type of property to register. This can be any type.</typeparam>
        /// <param name="name">Name of the property to register. The name used to register this property must match the name of a property defined in this class definition.</param>
        /// <param name="behaviors">Behaviors to be observed by this property. Pass in null to mute all possible behaviors.</param>
        /// <returns>The registered property. The return value of this method should be assigned to a readonly backing property.</returns>
        protected ModelProperty<T> RegisterProperty<T>(String name, Nullable<ModelPropertyBehaviors> behaviors)
        {
            return RegisterProperty<T>(name, behaviors, false);
        }

        /// <summary>
        /// Registers a property to this model.
        /// </summary>
        /// <typeparam name="T">Type of property to register. This can be any type.</typeparam>
        /// <param name="name">Name of the property to register. The name used to register this property must match the name of a property defined in this class definition.</param>
        /// <param name="behaviors">Behaviors to be observed by this property. Pass in null to mute all possible behaviors.</param>
        /// <param name="isWrapped">True to treat this as a wrapped property, otherwise false. If you are unsure, pass in false.</param>
        /// <returns>The registered property. The return value of this method should be assigned to a readonly backing property.</returns>
        protected ModelProperty<T> RegisterProperty<T>(String name, Nullable<ModelPropertyBehaviors> behaviors, Boolean isWrapped)
        {
            AssertCanRegisterProperty();
            Type propertyType = typeof(T);
            ModelProperty<T> property = null;

            Object[] parameterArray = new Object[3]
            {
                name,
                behaviors,
                isWrapped
            };

            if (propertyType == typeof(String))
            {
                property = RegisterStringPropertyMethod.Invoke(this, parameterArray) as ModelProperty<T>;
            }
            else if (propertyType.GetTypeInfo().IsValueType)
            {
                if (propertyType.GetTypeInfo().IsGenericType)
                {
                    Type valueType = propertyType.GenericTypeArguments.ElementAt(0);
                    TypeInfo typeInfo = valueType.GetTypeInfo();

                    if (!typeInfo.IsEnum)
                    {
                        //Check to see if it is a built in value type
                        if (typeInfo.ImplementedInterfaces.Contains(typeof(IEquatable<>).MakeGenericType(valueType)))
                        {
                            property = RegisterNullableValueTypeMethod.MakeGenericMethod(valueType).Invoke(this, parameterArray) as ModelProperty<T>;
                        }
                        else
                        {
                            //Or a custom struct
                            property = RegisterUnconstrainedMethod.MakeGenericMethod(typeof(T)).Invoke(this, parameterArray) as ModelProperty<T>;
                        }
                    }
                    else
                    {
                        property = RegisterNullableEnumMethod.MakeGenericMethod(valueType).Invoke(this, parameterArray) as ModelProperty<T>;
                    }
                }
                else
                {
                    TypeInfo typeInfo = propertyType.GetTypeInfo();

                    if (!typeInfo.IsEnum)
                    {
                        //Check to see if it is a built in value type
                        if (typeInfo.ImplementedInterfaces.Contains(typeof(IEquatable<T>)))
                        {
                            property = RegisterValueTypeMethod.MakeGenericMethod(typeof(T)).Invoke(this, parameterArray) as ModelProperty<T>;
                        }
                        else
                        {
                            //Or a custom struct
                            property = RegisterUnconstrainedMethod.MakeGenericMethod(typeof(T)).Invoke(this, parameterArray) as ModelProperty<T>;
                        }
                    }
                    else
                    {
                        property = RegisterEnumMethod.MakeGenericMethod(typeof(T)).Invoke(this, parameterArray) as ModelProperty<T>;
                    }
                }
            }
            else if (ModelBaseExtensions.CheckIsValidModelSubType(propertyType))
            {
                property = RegisterChildModelMethod.MakeGenericMethod(typeof(T)).Invoke(this, parameterArray) as ModelProperty<T>;
            }
            else
            {
                property = RegisterUnconstrainedMethod.MakeGenericMethod(typeof(T)).Invoke(this, parameterArray) as ModelProperty<T>;
            }

            return property;
        }

        /// <summary>
        /// Registers a model collection property to this model. This is the recommended method for registering properties that are collections of models.
        /// </summary>
        /// <typeparam name="T">Type of property to register. This can be any type derived from Caffeine.Core.ModelBase.</typeparam>
        /// <param name="name">Name of the property to register. The name used to register this property must match the name of a property defined in this class definition.</param>
        /// <returns>The registered property. The return value of this method should be assigned to a readonly backing property.</returns>
        protected ModelCollection<T> RegisterCollectionProperty<T>(String name) 
            where T : ModelBase, new()
        {
            return RegisterCollectionProperty<T>(name, StandardBehaviors);
        }

        /// <summary>
        /// Registers a model collection property to this model.
        /// </summary>
        /// <typeparam name="T">Type of property to register. This can be any type derived from Caffeine.Core.ModelBase.</typeparam>
        /// <param name="name">Name of the property to register. The name used to register this property must match the name of a property defined in this class definition.</param>
        /// <param name="behaviors">Behaviors to be observed by this property. Pass in null to mute all possible behaviors.</param>
        /// <returns>The registered property. The return value of this method should be assigned to a readonly backing property.</returns>
        protected ModelCollection<T> RegisterCollectionProperty<T>(String name, Nullable<ModelPropertyBehaviors> behaviors)
            where T : ModelBase, new()
        {
            return RegisterCollectionProperty<T>(name, behaviors, false);
        }

        /// <summary>
        /// Registers a model collection property to this model. 
        /// </summary>
        /// <typeparam name="T">Type of property to register. This can be any type derived from Caffeine.Core.ModelBase.</typeparam>
        /// <param name="name">Name of the property to register. The name used to register this property must match the name of a property defined in this class definition.</param>
        /// <param name="behaviors">Behaviors to be observed by this property. Pass in null to mute all possible behaviors.</param>
        /// <param name="isWrapped">Determines whether or not this property is considered wrapped. This method overload is not meant for typical use. 
        /// If you are unsure what value to pass to this parameter, pass false.</param>
        /// <returns>The registered property. The return value of this method should be assigned to a readonly backing property.</returns>
        protected ModelCollection<T> RegisterCollectionProperty<T>(String name, Nullable<ModelPropertyBehaviors> behaviors, Boolean isWrapped)
            where T : ModelBase, new()
        {
            AssertCanRegisterProperty();
            ModelCollection<T> collection = new ModelCollection<T>(this, name, behaviors, isWrapped);
            ModelProperties.Add(collection);
            return collection;
        }

        /// <summary>
        /// Registers a value type property to this model. Consider using the generic RegisterProperty&lt;T&gt; method instead.
        /// </summary>
        /// <typeparam name="T">Type of property to register. This can be any value type.</typeparam>
        /// <param name="name">Name of the property to register. The name used to register this property must match the name of a property defined in this class definition.</param>
        /// <returns>The registered property. The return value of this method should be assigned to a readonly backing property.</returns>
        protected ModelProperty<T> RegisterValueTypeProperty<T>(String name) where T : struct, IEquatable<T>
        {
            return RegisterValueTypeProperty<T>(name, StandardBehaviors);
        }

        /// <summary>
        /// Registers a value type property to this model. Consider using the generic RegisterProperty&lt;T&gt; method instead.
        /// </summary>
        /// <typeparam name="T">Type of property to register. This can be any value type.</typeparam>
        /// <param name="name">Name of the property to register. The name used to register this property must match the name of a property defined in this class definition.</param>
        /// <param name="behaviors">Behaviors to be observed by this property. Pass in null to mute all possible behaviors.</param>
        /// <returns>The registered property. The return value of this method should be assigned to a readonly backing property.</returns>
        protected ModelProperty<T> RegisterValueTypeProperty<T>(String name, Nullable<ModelPropertyBehaviors> behaviors) where T : struct, IEquatable<T>
        {
            return RegisterValueTypeProperty<T>(name, behaviors, false);
        }

        /// <summary>
        /// Registers a value type property to this model. Consider using the generic RegisterProperty&lt;T&gt; method instead.
        /// </summary>
        /// <typeparam name="T">Type of property to register. This can be any value type.</typeparam>
        /// <param name="name">Name of the property to register. The name used to register this property must match the name of a property defined in this class definition.</param>
        /// <param name="behaviors">Behaviors to be observed by this property. Pass in null to mute all possible behaviors.</param>
        /// <param name="isWrapped">Determines whether or not this property is considered wrapped. This method overload is not meant for typical use. 
        /// If you are unsure what value to pass to this parameter, pass false.</param>
        /// <returns>The registered property. The return value of this method should be assigned to a readonly backing property.</returns>
        protected ModelProperty<T> RegisterValueTypeProperty<T>(String name, Nullable<ModelPropertyBehaviors> behaviors, Boolean isWrapped) where T : struct, IEquatable<T>
        {
            AssertCanRegisterProperty();
            ValueTypeProperty<T> property = new ValueTypeProperty<T>(this, name, behaviors, isWrapped);
            ModelProperties.Add(property);
            return property;
        }

        /// <summary>
        /// Registers a nullable value type property to this model. Consider using the generic RegisterProperty&lt;T&gt; method instead.
        /// </summary>
        /// <typeparam name="T">Type of property to register. This can be any value type.</typeparam>
        /// <param name="name">Name of the property to register. The name used to register this property must match the name of a property defined in this class definition.</param>
        /// <param name="behaviors">Behaviors to be observed by this property. Pass in null to mute all possible behaviors.</param>
        /// <param name="isWrapped">Determines whether or not this property is considered wrapped. This method overload is not meant for typical use. 
        /// If you are unsure what value to pass to this parameter, pass false.</param>
        /// <returns>The registered property. The return value of this method should be assigned to a readonly backing property.</returns>
        protected ModelProperty<Nullable<T>> RegisterNullableValueTypeProperty<T>(String name, Nullable<ModelPropertyBehaviors> behaviors, Boolean isWrapped) where T : struct, IEquatable<T>
        {
            AssertCanRegisterProperty();
            NullableValueTypeProperty<T> property = new NullableValueTypeProperty<T>(this, name, behaviors, isWrapped);
            ModelProperties.Add(property);
            return property;
        }

        /// <summary>
        /// Registers a nullable value type property to this model. Consider using the generic RegisterProperty&lt;T&gt; method instead.
        /// </summary>
        /// <typeparam name="T">Type of property to register. This can be any value type.</typeparam>
        /// <param name="name">Name of the property to register. The name used to register this property must match the name of a property defined in this class definition.</param>
        /// <returns>The registered property. The return value of this method should be assigned to a readonly backing property.</returns>
        protected ModelProperty<Nullable<T>> RegisterNullableValueTypeProperty<T>(String name) where T : struct, IEquatable<T>
        {
            return RegisterNullableValueTypeProperty<T>(name, StandardBehaviors);
        }

        /// <summary>
        /// Registers a nullable value type property to this model. Consider using the generic RegisterProperty&lt;T&gt; method instead.
        /// </summary>
        /// <typeparam name="T">Type of property to register. This can be any value type.</typeparam>
        /// <param name="name">Name of the property to register. The name used to register this property must match the name of a property defined in this class definition.</param>
        /// <param name="behaviors">Behaviors to be observed by this property. Pass in null to mute all possible behaviors.</param>
        /// <returns>The registered property. The return value of this method should be assigned to a readonly backing property.</returns>
        protected ModelProperty<Nullable<T>> RegisterNullableValueTypeProperty<T>(String name, Nullable<ModelPropertyBehaviors> behaviors) where T : struct, IEquatable<T>
        {
            return RegisterNullableValueTypeProperty<T>(name, behaviors, false);
        }

        /// <summary>
        /// Registers an enum type property to this model. Consider using the generic RegisterProperty&lt;T&gt; method instead.
        /// </summary>
        /// <typeparam name="T">Type of property to register. This can be any enumeration type.</typeparam>
        /// <param name="name">Name of the property to register. The name used to register this property must match the name of a property defined in this class definition.</param>
        /// <returns>The registered property. The return value of this method should be assigned to a readonly backing property.</returns>
        protected ModelProperty<T> RegisterEnumProperty<T>(String name) where T : struct
        {
            return RegisterEnumProperty<T>(name, StandardBehaviors);
        }

        /// <summary>
        /// Registers an enum type property to this model. Consider using the generic RegisterProperty&lt;T&gt; method instead.
        /// </summary>
        /// <typeparam name="T">Type of property to register. This can be any enumeration type.</typeparam>
        /// <param name="name">Name of the property to register. The name used to register this property must match the name of a property defined in this class definition.</param>
        /// <param name="behaviors">Behaviors to be observed by this property. Pass in null to mute all possible behaviors.</param>
        /// <returns>The registered property. The return value of this method should be assigned to a readonly backing property.</returns>
        protected ModelProperty<T> RegisterEnumProperty<T>(String name, Nullable<ModelPropertyBehaviors> behaviors) where T : struct
        {
            return RegisterEnumProperty<T>(name, behaviors, false);
        }

        /// <summary>
        /// Registers an enum type property to this model. Consider using the generic RegisterProperty&lt;T&gt; method instead.
        /// </summary>
        /// <typeparam name="T">Type of property to register. This can be any enumeration type.</typeparam>
        /// <param name="name">Name of the property to register. The name used to register this property must match the name of a property defined in this class definition.</param>
        /// <param name="behaviors">Behaviors to be observed by this property. Pass in null to mute all possible behaviors.</param>
        /// <param name="isWrapped">Determines whether or not this property is considered wrapped. This method overload is not meant for typical use. 
        /// If you are unsure what value to pass to this parameter, pass false.</param>
        /// <returns>The registered property. The return value of this method should be assigned to a readonly backing property.</returns>
        protected ModelProperty<T> RegisterEnumProperty<T>(String name, Nullable<ModelPropertyBehaviors> behaviors, Boolean isWrapped) where T : struct
        {
            AssertCanRegisterProperty();
            EnumProperty<T> property = new EnumProperty<T>(this, name, behaviors, isWrapped);
            ModelProperties.Add(property);
            return property;
        }

        /// <summary>
        /// Registers a nullable enum type property to this model. Consider using the generic RegisterProperty&lt;T&gt; method instead.
        /// </summary>
        /// <typeparam name="T">Type of property to register. This can be any enumeration type.</typeparam>
        /// <param name="name">Name of the property to register. The name used to register this property must match the name of a property defined in this class definition.</param>
        /// <returns>The registered property. The return value of this method should be assigned to a readonly backing property.</returns>
        protected ModelProperty<Nullable<T>> RegisterNullableEnumProperty<T>(String name) where T : struct
        {
            return RegisterNullableEnumProperty<T>(name, StandardBehaviors);
        }

        /// <summary>
        /// Registers a nullable enum type property to this model. Consider using the generic RegisterProperty&lt;T&gt; method instead.
        /// </summary>
        /// <typeparam name="T">Type of property to register. This can be any enumeration type.</typeparam>
        /// <param name="name">Name of the property to register. The name used to register this property must match the name of a property defined in this class definition.</param>
        /// <param name="behaviors">Behaviors to be observed by this property. Pass in null to mute all possible behaviors.</param>
        /// <returns>The registered property. The return value of this method should be assigned to a readonly backing property.</returns>
        protected ModelProperty<Nullable<T>> RegisterNullableEnumProperty<T>(String name, Nullable<ModelPropertyBehaviors> behaviors) where T : struct
        {
            return RegisterNullableEnumProperty<T>(name, behaviors, false);
        }

        /// <summary>
        /// Registers a nullable enum type property to this model. Consider using the generic RegisterProperty&lt;T&gt; method instead.
        /// </summary>
        /// <typeparam name="T">Type of property to register. This can be any enumeration type.</typeparam>
        /// <param name="name">Name of the property to register. The name used to register this property must match the name of a property defined in this class definition.</param>
        /// <param name="behaviors">Behaviors to be observed by this property. Pass in null to mute all possible behaviors.</param>
        /// <param name="isWrapped">Determines whether or not this property is considered wrapped. This method overload is not meant for typical use. 
        /// If you are unsure what value to pass to this parameter, pass false.</param>
        /// <returns>The registered property. The return value of this method should be assigned to a readonly backing property.</returns>
        protected ModelProperty<Nullable<T>> RegisterNullableEnumProperty<T>(String name, Nullable<ModelPropertyBehaviors> behaviors, Boolean isWrapped) where T : struct
        {
            AssertCanRegisterProperty();
            NullableEnumProperty<T> property = new NullableEnumProperty<T>(this, name, behaviors, isWrapped);
            ModelProperties.Add(property);
            return property;
        }

        /// <summary>
        /// Registers a string property to this model. Consider using the generic RegisterProperty&lt;T&gt; method instead.
        /// </summary>
        /// <param name="name">Name of the property to register. The name used to register this property must match the name of a property defined in this class definition.</param>
        /// <returns>The registered property. The return value of this method should be assigned to a readonly backing property.</returns>
        protected ModelProperty<String> RegisterStringProperty(String name)
        {
            return RegisterStringProperty(name, StandardBehaviors);
        }

        /// <summary>
        /// Registers a string property to this model. Consider using the generic RegisterProperty&lt;T&gt; method instead.
        /// </summary>
        /// <param name="name">Name of the property to register. The name used to register this property must match the name of a property defined in this class definition.</param>
        /// <param name="behaviors">Behaviors to be observed by this property. Pass in null to mute all possible behaviors.</param>
        /// <returns>The registered property. The return value of this method should be assigned to a readonly backing property.</returns>
        protected ModelProperty<String> RegisterStringProperty(String name, Nullable<ModelPropertyBehaviors> behaviors)
        {
            return RegisterStringProperty(name, behaviors, false);
        }

        /// <summary>
        /// Registers a string property to this model. Consider using the generic RegisterProperty&lt;T&gt; method instead.
        /// </summary>
        /// <param name="name">Name of the property to register. The name used to register this property must match the name of a property defined in this class definition.</param>
        /// <param name="behaviors">Behaviors to be observed by this property. Pass in null to mute all possible behaviors.</param>
        /// <param name="isWrapped">Determines whether or not this property is considered wrapped. This method overload is not meant for typical use. 
        /// If you are unsure what value to pass to this parameter, pass false.</param>
        /// <returns>The registered property. The return value of this method should be assigned to a readonly backing property.</returns>
        protected ModelProperty<String> RegisterStringProperty(String name, Nullable<ModelPropertyBehaviors> behaviors, Boolean isWrapped)
        {
            AssertCanRegisterProperty();
            StringProperty property = new StringProperty(this, name, behaviors, isWrapped);
            ModelProperties.Add(property);
            return property;
        }

        /// <summary>
        /// Registers a child model property to this model. Consider using the generic RegisterProperty&lt;T&gt; method instead.
        /// </summary>
        /// <typeparam name="T">Type of property to register. This can be any type derived from Caffeine.Core.ModelBase.</typeparam>
        /// <param name="name">Name of the property to register. The name used to register this property must match the name of a property defined in this class definition.</param>
        /// <returns>The registered property. The return value of this method should be assigned to a readonly backing property.</returns>
        protected ModelProperty<T> RegisterChildModelProperty<T>(String name) 
            where T : ModelBase, new()
        {
            return RegisterChildModelProperty<T>(name, StandardBehaviors);
        }

        /// <summary>
        /// Registers a child model property to this model. Consider using the generic RegisterProperty&lt;T&gt; method instead.
        /// </summary>
        /// <typeparam name="T">Type of property to register. This can be any type derived from Caffeine.Core.ModelBase.</typeparam>
        /// <param name="name">Name of the property to register. The name used to register this property must match the name of a property defined in this class definition.</param>
        /// <param name="behaviors">Behaviors to be observed by this property. Pass in null to mute all possible behaviors.</param>
        /// <returns>The registered property. The return value of this method should be assigned to a readonly backing property.</returns>
        protected ModelProperty<T> RegisterChildModelProperty<T>(String name, Nullable<ModelPropertyBehaviors> behaviors) 
            where T : ModelBase, new()
        {
            return RegisterChildModelProperty<T>(name, behaviors, false);
        }

        /// <summary>
        /// Registers a child model property to this model. Consider using the generic RegisterProperty&lt;T&gt; method instead.
        /// </summary>
        /// <typeparam name="T">Type of property to register. This can be any type derived from Caffeine.Core.ModelBase.</typeparam>
        /// <param name="name">Name of the property to register. The name used to register this property must match the name of a property defined in this class definition.</param>
        /// <param name="behaviors">Behaviors to be observed by this property. Pass in null to mute all possible behaviors.</param>
        /// <param name="isWrapped">Determines whether or not this property is considered wrapped. This method overload is not meant for typical use. 
        /// If you are unsure what value to pass to this parameter, pass false.</param>
        /// <returns>The registered property. The return value of this method should be assigned to a readonly backing property.</returns>
        protected ModelProperty<T> RegisterChildModelProperty<T>(String name, Nullable<ModelPropertyBehaviors> behaviors, Boolean isWrapped) 
            where T : ModelBase, new()
        {
            AssertCanRegisterProperty();
            ModelProperty<T> property = new ModelChildProperty<T>(this, name, behaviors, isWrapped);
            ModelProperties.Add(property);
            return property;
        }

        /// <summary>
        /// Registers a property of any type to this model. Consider using the generic RegisterProperty&lt;T&gt; method instead.
        /// </summary>
        /// <typeparam name="T">Type of property to register. This can be any type.</typeparam>
        /// <param name="name">Name of the property to register. The name used to register this property must match the name of a property defined in this class definition.</param>
        /// <returns>The registered property. The return value of this method should be assigned to a readonly backing property.</returns>
        protected ModelProperty<T> RegisterUnconstrainedProperty<T>(String name)
        {
            return RegisterUnconstrainedProperty<T>(name, StandardBehaviors);
        }

        /// <summary>
        /// Registers a property of any type to this model. Consider using the generic RegisterProperty&lt;T&gt; method instead.
        /// </summary>
        /// <typeparam name="T">Type of property to register. This can be any type.</typeparam>
        /// <param name="name">Name of the property to register. The name used to register this property must match the name of a property defined in this class definition.</param>
        /// <param name="behaviors">Behaviors to be observed by this property. Pass in null to mute all possible behaviors.</param>
        /// <returns>The registered property. The return value of this method should be assigned to a readonly backing property.</returns>
        protected ModelProperty<T> RegisterUnconstrainedProperty<T>(String name, Nullable<ModelPropertyBehaviors> behaviors)
        {
            return RegisterUnconstrainedProperty<T>(name, behaviors, false);
        }

        /// <summary>
        /// Registers a property of any type to this model. Consider using the generic RegisterProperty&lt;T&gt; method instead.
        /// </summary>
        /// <typeparam name="T">Type of property to register. This can be any type.</typeparam>
        /// <param name="name">Name of the property to register. The name used to register this property must match the name of a property defined in this class definition.</param>
        /// <param name="behaviors">Behaviors to be observed by this property. Pass in null to mute all possible behaviors.</param>
        /// <param name="isWrapped">Determines whether or not this property is considered wrapped. This method overload is not meant for typical use. 
        /// If you are unsure what value to pass to this parameter, pass false.</param>
        /// <returns>The registered property. The return value of this method should be assigned to a readonly backing property.</returns>
        protected ModelProperty<T> RegisterUnconstrainedProperty<T>(String name, Nullable<ModelPropertyBehaviors> behaviors, Boolean isWrapped)
        {
            AssertCanRegisterProperty();
            UnconstrainedProperty<T> property = new UnconstrainedProperty<T>(this, name, behaviors, isWrapped);
            ModelProperties.Add(property);
            return property;
        }

        /// <summary>
        /// Registers a computed property that represents the combined values from one or more other model properties registered with this model
        /// </summary>
        /// <typeparam name="PropertyType">Any primitive type. Ex) String, Int32, Decimal, Char, etc.</typeparam>
        /// <param name="name">Name of the property to register. The name used to register this property must match the name of a property defined in this class definition.</param>
        /// <param name="computation">A function that takes all the registered model properties and returns a computed value</param>
        /// <param name="properties">All of the model properties that are to be considered when computing the value for this property. Only properties that are registered
        /// to this model and contain primitive types can be part of a computed property</param>
        /// <returns>The registered property. The return value of this method should be assigned to a readonly backing property.</returns>
        protected ModelProperty<PropertyType> RegisterComputedProperty<PropertyType>
        (
            String name,
            Func<IEnumerable<ModelProperty<PropertyType>>, PropertyType, PropertyType> computation,
            params ModelProperty<PropertyType>[] properties
        )
        {
            return RegisterComputedProperty(name, computation, StandardBehaviors, properties);
        }

        /// <summary>
        /// Registers a computed property that represents the combined values from one or more other model properties registered with this model
        /// </summary>
        /// <typeparam name="PropertyType">Any primitive type. Ex) String, Int32, Decimal, Char, etc.</typeparam>
        /// <typeparam name="RegisteredPropertyType">Any primitive type. Ex) String, Int32, Decimal, Char, etc.</typeparam>
        /// <param name="name">Name of the property to register. The name used to register this property must match the name of a property defined in this class definition.</param>
        /// <param name="computation">A function that takes all the registered model properties and returns a computed value</param>
        /// <param name="properties">All of the model properties that are to be considered when computing the value for this property. Only properties that are registered
        /// to this model and contain primitive types can be part of a computed property</param>
        /// <returns>The registered property. The return value of this method should be assigned to a readonly backing property.</returns>
        protected ModelProperty<PropertyType> RegisterComputedProperty<PropertyType, RegisteredPropertyType>
        (
            String name,
            Func<IEnumerable<ModelProperty<RegisteredPropertyType>>, RegisteredPropertyType, PropertyType> computation, 
            params ModelProperty<RegisteredPropertyType>[] properties
        )
        {
            return RegisterComputedProperty(name, computation, StandardBehaviors, properties);
        }

        /// <summary>
        /// Registers a computed property that represents the combined values from one or more other model properties registered with this model
        /// </summary>
        /// <typeparam name="PropertyType">Any primitive type. Ex) String, Int32, Decimal, Char, etc.</typeparam>
        /// <typeparam name="RegisteredPropertyType">Any primitive type. Ex) String, Int32, Decimal, Char, etc.</typeparam>
        /// <param name="name">Name of the property to register. The name used to register this property must match the name of a property defined in this class definition.</param>
        /// <param name="computation">A function that takes all the registered model properties and returns a computed value</param>
        /// <param name="behaviors">Behaviors to be observed by this property. Pass in null to mute all possible behaviors.</param>
        /// <param name="properties">All of the model properties that are to be considered when computing the value for this property. Only properties that are registered
        /// to this model and contain primitive types can be part of a computed property</param>
        /// <returns>The registered property. The return value of this method should be assigned to a readonly backing property.</returns>
        protected ModelProperty<PropertyType> RegisterComputedProperty<PropertyType, RegisteredPropertyType>
        (
            String name,
            Func<IEnumerable<ModelProperty<RegisteredPropertyType>>, RegisteredPropertyType, PropertyType> computation, 
            Nullable<ModelPropertyBehaviors> behaviors,
            params ModelProperty<RegisteredPropertyType>[] properties
        )
        {
            return RegisterComputedProperty(name, computation, behaviors, false, properties);
        }

        /// <summary>
        /// Registers a computed property that represents the combined values from one or more other model properties registered with this model
        /// </summary>
        /// <typeparam name="PropertyType">Any primitive type. Ex) String, Int32, Decimal, Char, etc.</typeparam>
        /// <typeparam name="RegisteredPropertyType">Any primitive type. Ex) String, Int32, Decimal, Char, etc.</typeparam>
        /// <param name="name">Name of the property to register. The name used to register this property must match the name of a property defined in this class definition.</param>
        /// <param name="computation">A function that takes all the registered model properties and returns a computed value</param>
        /// <param name="behaviors">Behaviors to be observed by this property. Pass in null to mute all possible behaviors.</param>
        /// <param name="isWrapped">Determines whether or not this property is considered wrapped. This method overload is not meant for typical use. 
        /// If you are unsure what value to pass to this parameter, pass false.</param>
        /// <param name="properties">All of the model properties that are to be considered when computing the value for this property. Only properties that are registered
        /// to this model and contain primitive types can be part of a computed property</param>
        /// <returns>The registered property. The return value of this method should be assigned to a readonly backing property.</returns>
        protected ModelProperty<PropertyType> RegisterComputedProperty<PropertyType, RegisteredPropertyType>
        (
            String name,
            Func<IEnumerable<ModelProperty<RegisteredPropertyType>>, RegisteredPropertyType, PropertyType> computation,  
            Nullable<ModelPropertyBehaviors> behaviors, 
            Boolean isWrapped,
            params ModelProperty<RegisteredPropertyType>[] properties
        )
        {
            if (computation == null)
            {
                throw new ArgumentNullException("computation");
            }

            if (properties.Length == 0)
            {
                throw new ArgumentException("You must pass in at least one property", "properties");
            }

            foreach (ModelProperty<RegisteredPropertyType> property in properties)
            {
                if (property == null)
                {
                    throw new ArgumentException("Cannot register a null property");
                }

                if (property.IsModelProperty)
                {
                    throw new ArgumentException("Cannot register properties that contain child models");
                }

                if (property.Parent != this)
                {
                    throw new ArgumentException("Can only create computed properties with model properties from the same parent");
                }
            }

            ComputedModelProperty<RegisteredPropertyType, PropertyType> computedProperty = new ComputedModelProperty<RegisteredPropertyType, PropertyType>
            (
                this, 
                name, 
                behaviors, 
                isWrapped, 
                computation, 
                properties
            );
            
            ModelProperties.Add(computedProperty);
            return computedProperty;
        }

        /// <summary>
        /// Register a command for this model
        /// </summary>
        /// <typeparam name="T">Command parameter type</typeparam>
        /// <param name="executeAction">Method to execute for this command</param>
        /// <returns>The registered command</returns>
        protected ModelCommand<T> RegisterCommand<T>(Action<ModelBase, T> executeAction)
        {
            return RegisterCommand<T>(executeAction, CommandDefaultCanExecuteCheck<T>);
        }

        /// <summary>
        /// Register a command for this model
        /// </summary>
        /// <typeparam name="T">Command parameter type</typeparam>
        /// <param name="executeAction">Method to execute for this command</param>
        /// <param name="canExecutePredicate">Method to query whether or not the command can be executed</param>
        /// <returns>The registered command</returns>
        protected ModelCommand<T> RegisterCommand<T>(Action<ModelBase, T> executeAction, CanExecuteCommandDel<T> canExecutePredicate)
        {
            if (executeAction == null)
            {
                throw new ArgumentNullException("executeAction");
            }

            if (canExecutePredicate == null)
            {
                throw new ArgumentNullException("canExecutePredicate");
            }

            ModelCommand<T> registeredCommand = new ModelCommand<T>(this, executeAction, canExecutePredicate);
            ModelCommands.Add(registeredCommand);
            return registeredCommand;
        }

        #endregion

        /// <summary>
        /// Creates a model with the same model factory that created this model
        /// </summary>
        /// <typeparam name="T">Model type to create</typeparam>
        /// <returns>New model</returns>
        protected T CreateModel<T>()
            where T : ModelBase, new()
        {
            return ParentFactory.CreateModel<T>();
        }

        /// <summary>
        /// Override to provide alternate implementation for validation error list
        /// </summary>
        /// <returns>Implementation of IList&lt;ValidationError&gt;</returns>
        protected virtual IList<ValidationError> CreateValidationErrorList()
        {
            return new List<ValidationError>();
        }

        #endregion

        #region Private

        private static readonly Type ModelBackingPropertyAttributeType;

        private static readonly Type ModelCommandBackingPropertyAttributeType;

        private static readonly Type CanExecuteCommandDelType;

        private static readonly Type ActionType;

        private static readonly Type ModelBaseType;

        private static readonly MethodInfo RegisterPropertyMethod;

        private static readonly MethodInfo RegisterCollectionPropertyMethod;

        private static readonly MethodInfo RegisterStringPropertyMethod;

        private static readonly MethodInfo RegisterNullableValueTypeMethod;

        private static readonly MethodInfo RegisterValueTypeMethod;

        private static readonly MethodInfo RegisterChildModelMethod;

        private static readonly MethodInfo RegisterUnconstrainedMethod;

        private static readonly MethodInfo RegisterEnumMethod;

        private static readonly MethodInfo RegisterNullableEnumMethod;

        private static readonly MethodInfo RegisterCommandMethod;

        private void AssignIsParentDirtyWithin()
        {
            IsParentDirtyWithin = IsDirty ||
                                  IsDirtyWithin;
        }

        private void AssignIsParentInvalidWithin()
        {
            IsParentInvalidWithin = IsInvalid ||
                                    IsInvalidWithin;
        }

        private void AssignIsRemovedFromParent()
        {
            IsRemovedFromParent = IsRemovedFromCollection ||
                                  IsRemovedFromParentProperty;
        }

        private void AssertCanRegisterProperty()
        {
            if (IsInitialized)
            {
                throw new InvalidOperationException("Properties cannot be registered after a model has been initialized. Model properties are meant to be registered only in the constructors of models.");
            }
        }

        private void ApplyMetadata(ModelMetadata metadata, ModelPropertyBase parentProperty)
        {
            if (parentProperty != null)
            {
                _parent = parentProperty.Parent;
                _parentProperty = parentProperty;

                //IMPORTANT: If the model has no parents, then it cannot be in a collection or assigned to a scalar model property.
                //>>>>>START Parent Specific Properties<<<<<<
                _hasParent = metadata.HasParent;
                _isRemovedFromParent = metadata.IsRemovedFromParent;
                
                _isAddedToCollection = metadata.IsAddedToCollection;
                _isCommittedToCollection = metadata.IsCommittedToCollection;
                _isRemovedFromCollection = metadata.IsRemovedFromCollection;
                
                _isAssignedToParentProperty = metadata.IsAssignedToParentProperty;
                _isCommittedToParentProperty = metadata.IsCommittedToParentProperty;
                _isRemovedFromParentProperty = metadata.IsRemovedFromParentProperty;
                //>>>>>END Parent Specific Properties<<<<<<
            }

            _isDirty = metadata.IsDirty;
            _isDirtyWithin = metadata.IsDirtyWithin;
            _isParentDirtyWithin = metadata.IsParentDirtyWithin;
            _isInvalid = metadata.IsInvalid;
            _isInvalidWithin = metadata.IsInvalidWithin;
            _isParentInvalidWithin = metadata.IsParentInvalidWithin;
            _name = metadata.Name;
            _uniqueId = metadata.UniqueId;
            _dirtyPropertyCount = metadata.DirtyPropertyCount;
            _invalidPropertyCount = metadata.InvalidPropertyCount;
            DirtyWithinPropertyCount = metadata.DirtyWithinPropertyCount;
            InvalidWithinPropertyCount = metadata.InvalidWithinPropertyCount;

            if (metadata.ExtensibleData != null)
            {
                foreach (KeyValuePair<String, Object> extensibleData in metadata.ExtensibleData)
                {
                    ExtensibleData.Add(extensibleData);
                }
            }

            if (metadata.ScalarProperties != null)
            {
                foreach (ModelScalarPropertyMetadata propertyMetadata in metadata.ScalarProperties)
                {
                    ModelScalarPropertyBase property = ModelProperties[propertyMetadata.PropertyIndex] as ModelScalarPropertyBase;
                    property.ApplyScalarPropertyMetadata(propertyMetadata, ParentFactory);
                }
            }

            if (metadata.CollectionProperties != null)
            {
                foreach (ModelCollectionMetadata propertyMetadata in metadata.CollectionProperties)
                {
                    ModelCollectionBase property = ModelProperties[propertyMetadata.PropertyIndex] as ModelCollectionBase;
                    property.ApplyCollectionMetadata(propertyMetadata, ParentFactory);
                }
            }

            if (metadata.Commands != null)
            {
                for(Int32 index = 0; index < metadata.Commands.Length; index++)
                {
                    ModelCommands[index].ApplyMetadata(metadata.Commands[index]);
                }
            }
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "ValueType")]
        private void RegisterAdornedFields()
        {
            foreach (FieldInfo field in GetType().GetRuntimeFields())
            {
                foreach (Object attribute in GetAttributes(field, false))
                {
                    if (attribute.GetType() == ModelBackingPropertyAttributeType)
                    {
                        ModelBackingPropertyAttribute vmpAttribute = attribute as ModelBackingPropertyAttribute;

                        Object[] parameters = new Object[]
                        {
                            vmpAttribute.PropertyName,
                            vmpAttribute.Behaviors,
                            vmpAttribute.IsWrapped
                        };

                        try
                        {
                            if (!vmpAttribute.IsCollectionProperty)
                            {
                                field.SetValue(this, RegisterPropertyMethod.MakeGenericMethod(vmpAttribute.ValueType).Invoke(this, parameters));
                            }
                            else
                            {
                                field.SetValue(this, RegisterCollectionPropertyMethod.MakeGenericMethod(vmpAttribute.ValueType).Invoke(this, parameters));
                            }
                        }
                        catch (Exception ex)
                        {
                            throw new InvalidOperationException
                            (
                                String.Format
                                (
                                    "Failed trying to register field '{0}' on type {1}. The {2} values where: (PropertyName - {3}) (Behaviors - {4}) (ValueType - {5}) (IsCollectionProperty - {6}). One or more of these values is probably incorrect. Check inner exception for more details.",
                                    field.Name,
                                    GetType().FullName,
                                    ModelBackingPropertyAttributeType.FullName,
                                    vmpAttribute.PropertyName,
                                    vmpAttribute.Behaviors,
                                    vmpAttribute.ValueType.FullName,
                                    vmpAttribute.IsCollectionProperty
                                ),
                                ex
                            );
                        }
                    }
                }
            }
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
        private void RegisterAdornedCommands()
        {
            Type currentType = GetType();

            foreach (FieldInfo field in currentType.GetRuntimeFields())
            {
                foreach (Object attribute in GetAttributes(field, false))
                {
                    if (attribute.GetType() == ModelCommandBackingPropertyAttributeType)
                    {
                        ModelCommandBackingPropertyAttribute vmcAttribute = attribute as ModelCommandBackingPropertyAttribute;

                        MethodInfo actionMethod = ModelBase.GetMethod
                        (
                            currentType,
                            vmcAttribute.ActionMethodName,
                            new Type[]
                            {
                                ModelBaseType,
                                vmcAttribute.ParameterType
                            }
                        );

                        Delegate actionMethodDelegate = actionMethod.CreateDelegate
                        (
                            ActionType.MakeGenericType(ModelBaseType, vmcAttribute.ParameterType),
                            this
                        );

                        MethodInfo predicateMethod = ModelBase.GetMethod
                        (
                            currentType,
                            vmcAttribute.PredicateMethodName,
                            new Type[]
                            {
                                ModelBaseType,
                                vmcAttribute.ParameterType
                            } 
                        );

                        Delegate predicateMethodDelegate = predicateMethod.CreateDelegate
                        (
                            CanExecuteCommandDelType.MakeGenericType(vmcAttribute.ParameterType),
                            this
                        );

                        Object[] parameters = new Object[]
                        {
                            actionMethodDelegate,
                            predicateMethodDelegate
                        };

                        try
                        {
                            field.SetValue(this, RegisterCommandMethod.MakeGenericMethod(vmcAttribute.ParameterType).Invoke(this, parameters));
                        }
                        catch (Exception ex)
                        {
                            throw new InvalidOperationException
                            (
                                String.Format
                                (
                                    "Failed trying to register command '{0}' on type {1}. The {2} values where: (ActionMethodName - {3}) (PredicateMethodName - {4}) (ParameterType - {5}). One or more of these values is probably incorrect. Check inner exception for more details.",
                                    field.Name,
                                    GetType().FullName,
                                    ModelCommandBackingPropertyAttributeType.FullName,
                                    vmcAttribute.ActionMethodName,
                                    vmcAttribute.PredicateMethodName,
                                    vmcAttribute.ParameterType.FullName
                                ),
                                ex
                            );
                        }
                    }
                }
            }
        }

        private static IEnumerable<Object> GetAttributes(FieldInfo field, Boolean inherit)
        {
            return field.GetCustomAttributes(inherit).OfType<Object>();
        }

        private static MethodInfo GetMethod(Type type, String methodName, Type[] parameterTypes)
        {
            MethodInfo returnMethod = null;

            foreach (MethodInfo method in type.GetRuntimeMethods())
            {
                if (method.Name == methodName)
                {
                    ParameterInfo[] parameters = method.GetParameters();

                    if (parameters.Length == parameterTypes.Length)
                    {
                        Boolean parametersMatch = true;

                        for (Int32 parameterIndex = 0; parameterIndex < parameters.Length; parameterIndex++)
                        {
                            if (parameters[parameterIndex].ParameterType != parameterTypes[parameterIndex])
                            {
                                parametersMatch = false;
                                break;
                            }
                        }

                        if (parametersMatch)
                        {
                            returnMethod = method;
                            break;
                        }
                    }
                }
            }

            return returnMethod;
        }

        private static MethodInfo GetRegisterCommandMethod(Type type)
        {
            MethodInfo returnMethod = null;

            foreach (MethodInfo method in type.GetTypeInfo().DeclaredMethods)
            {
                if (method.Name == "RegisterCommand")
                {
                    ParameterInfo[] parameters = method.GetParameters();

                    if (parameters.Length == 2)
                    {
                        returnMethod = method;
                        break;
                    }
                }
            }

            return returnMethod;
        }

        private static Boolean CheckIsDirty(ModelPropertyBase property)
        {
            return property.IsDirty;
        }

        private static Boolean CheckIsNotDirty(ModelPropertyBase property)
        {
            return !property.IsDirty;
        }

        #endregion
    }
}